<?php

declare (strict_types=1);

namespace magein\pay\platform;

use EasyWeChat\Kernel\Exceptions\Exception;
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
use EasyWeChat\Payment\Application;
use EasyWeChat\Payment\Kernel\Exceptions\InvalidSignException;
use GuzzleHttp\Exception\GuzzleException;
use magein\pay\data\NoticeData;
use magein\pay\data\PayOrderData;
use magein\pay\PayConfig;
use magein\pay\PayException;
use magein\pay\PayFactory;
use magein\pay\PayPlatform;
use EasyWeChat\Factory;
use magein\utils\Result;

class WxPay extends PayPlatform
{
    const TRADE_TYPE_JSAPI = 'JSAPI';

    const TRADE_TYPE_NATIVE = 'NATIVE';

    /**
     * @var Application|null
     */
    protected $payment = null;

    /**
     * @return string
     */
    public static function getPlatformName(): string
    {
        return 'wx';
    }

    /**
     * @param $data
     * @return array
     */
    public function matchConfig($data): array
    {
        $appid = $data['appid'] ?? '';
        $merchant_id = $data['merchant_id'] ?? '';
        // 收款账号配置
        $accounts = PayConfig::platformAccount(self::getPlatformName());
        $config = [];
        if ($accounts && is_array($accounts)) {
            foreach ($accounts as $account) {
                if ($account['appid'] == $appid && $account['merchant_id'] == $merchant_id) {
                    $config = $account;
                    break;
                }
            }
        }

        return $config;
    }

    /**
     * @param array $config
     * @return Application
     */
    public function init(array $config = []): Application
    {
        $config = [
            'app_id' => $config['appid'] ?? '',
            'mch_id' => $config['merchant_id'] ?? '',
            'key' => $config['key'] ?? '',
        ];

        $this->payment = Factory::payment($config);

        return $this->payment;
    }

    /**
     * @param array $config
     * @return \EasyWeChat\OfficialAccount\Application
     */
    public function officialAccount(array $config = []): \EasyWeChat\OfficialAccount\Application
    {
        return Factory::officialAccount(
            [
                'app_id' => $config['appid'] ?? '',
                'secret' => $config['secret'] ?? '',
            ]
        );
    }

    /**
     * @param PayOrderData $pay_order_data
     * @return Result
     * @throws PayException
     */
    public function unify(PayOrderData $pay_order_data): Result
    {
        // https://easywechat.vercel.app/5.x/payment/order.html#%E7%BB%9F%E4%B8%80%E4%B8%8B%E5%8D%95
        // https://easywechat.com/5.x/payment/order.html#%E7%BB%9F%E4%B8%80%E4%B8%8B%E5%8D%95

        // 微信文档 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
        $message = '';
        $result = [];

        try {

            PayFactory::order()->create($pay_order_data);

            $result = $this->payment->order->unify([
                'body' => $pay_order_data->getSubject(),
                'out_trade_no' => $pay_order_data->getPayNo(),
                'total_fee' => $pay_order_data->getTotalAmount(),
                'notify_url' => $this->getNotifyUrl($pay_order_data->getPayNo()),
                'trade_type' => $pay_order_data->getTradeType(),
                'openid' => $pay_order_data->getOpenId()
            ]);

        } catch (PayException $exception) {
            $res = $exception->toResult();
            $message = $res->getMsg();
        } catch (InvalidArgumentException $exception) {
            $message = $exception->getMessage();
        } catch (InvalidConfigException|GuzzleException $exception) {
            $message = $exception->getMessage();
        }

        /**
         * 参数错误，如 mch_id、key这些参数长度错误，抛出异常
         */
        if ($message) {
            PayFactory::log()->debug(sprintf("  order_no:%s\n  exception:%s", $pay_order_data->getOrderNo(), $message));
            PayException::code(400301);
        }

        /**
         * 签名出现错误，如 mch_id、key、trade_type参数值错误
         */
        if (($result['return_code'] ?? '') == 'FAIL') {
            PayFactory::log()->debug(sprintf("  order_no:%s\n  return_code_fail:%s", $pay_order_data->getOrderNo(), $result['return_msg'] ?? ''));
            PayException::code(400303);
        }

        /**
         * return_code 等于 SUCCESS 但是 result_code 等于 fail
         *
         * 如:无效的openid
         */
        if (($result['result_code'] ?? '') == 'FAIL') {
            PayFactory::log()->debug(sprintf("  order_no:%s\n  appid:%s\n  mch_id:%s\n  err_code_des:%s",
                    $pay_order_data->getOrderNo(),
                    $pay_order_data->getAppid(),
                    $pay_order_data->getMerchantId(),
                    $result['err_code_des'] ?? ''
                )
            );
            PayException::code(400305);
        }

        /**
         * native支付则返回 code_url
         *
         * code_url：weixin://wxpay/bizpayurl?pr=3st5UvSz3
         */
        if ($pay_order_data->getTradeType() == self::TRADE_TYPE_NATIVE) {
            return Result::success($result['code_url']);
        }

        // jsapi支付需要的是预支付id
        $prepayId = $result['prepay_id'];

        return Result::success($this->payment->jssdk->bridgeConfig($prepayId, false));
    }

    public function queryByPayNo($pay_no)
    {
        try {
            $result = $this->payment->order->queryByOutTradeNumber($pay_no);
        } catch (\Exception $exception) {
            $result = null;
        }

        return $result;
    }

    /**
     * @param $data
     * @return NoticeData
     */
    public function notifyData($data): NoticeData
    {

        $notify = new NoticeData();
        $notify->setPayNo($data['out_trade_no'] ?? '');
        $notify->setTradeNo($data['transaction_id'] ?? '');
        $notify->setNotifyData($data);
        if ($data['time_end'] ?? '') {
            $time_end = date('Y-m-d H:i:s', strtotime($data['time_end']));
            $notify->setNotifyDate($time_end);
        }

        return $notify;
    }

    /**
     * @param $data
     * @return NoticeData
     */
    public function handleNotify($data): NoticeData
    {
        $notify = $this->notifyData($data);
        $notify->setOk(false);

        try {

            $response = $this->payment->handlePaidNotify(function ($data, $fail) use (&$notify) {

                if (($data['return_code'] ?? '') == 'FAIL') {
                    return $fail('请稍后再通知我');
                }

                $notify->setPayNo($data['out_trade_no']);
                $notify->setTradeNo($data['transaction_id']);
                $notify->setNotifyData($data);
                $notify->setOk(true);

                if (($data['result_code'] ?? '') == 'SUCCESS') {
                    $notify->success();
                } else {
                    $notify->fail($data['err_code_des'] ?? '');
                }

                return true;
            });

            $notify->setResponse($response);

        } catch (Exception|InvalidSignException|InvalidArgumentException $exception) {

        }

        return $notify;
    }

    /**
     * 微信授权
     * @param $account
     * @param $pay_no
     * @return string
     * @throws PayException
     */
    public function oauth($account, $pay_no): string
    {
        $redirect = PayFactory::wxOauth()->getOauthRedirect(compact('account', 'pay_no'));

        if (empty($redirect)) {
            PayException::code(400307);
        }

        $config = PayConfig::platformAccount(self::getPlatformName(), $account);

        return $this->officialAccount($config)->oauth->scopes(['snsapi_userinfo'])->redirect($redirect);
    }
}